Eddie's Lessons
Beginners Programming
It is amazing how long a person can be using something like a programming language and yet there is more to learn all the time. Alyce helped bring this home to me this month while we were discussing passing values into a DLL. I sure thought I understood the subject matter, but I came up wanting.
At the heart of the discussion is how the computer handles variables. These are of course stored in the computers Random Access Memory (RAM) during runtime. Each variable has both a memory location and a value. These details are hidden from us in Liberty Basic, but are important to understand as you begin to use API and DLL calls.
The memory location is like an address and that is what gets handled most of the time in the background. It points to the beginning byte where data starts. If the data is numeric then it will have a fixed size. In strictly typed languages like C and Pascal you would know the size by the data type (Integer, Long, Double, Float, etc). Liberty Basic uses what make sense - it is not required to know more. When working with strings the size is variable depending on the length of the string. Strings are normally terminated with a null (ASCII zero) in memory to indicate where they end.
The discussion of the length of the variable is interesting from an academic perspective, but has basically no relevance to us in the LB world. What is of importance is that we know where the variable is stored and its type. When we begin passing these variables into DLL call we can choose to either pass a COPY of the variable or the REAL THING.
If we choose to pass a copy it is like going to a photocopier and duplicating the variable. It does not matter what the called DLL does to the variable we copied, our original is still in our hands and remains unchanged.
If we choose to pass the real thing then the calling DLL can literally change the value of our variable. When the DLL gets done with it we may have a completely different value to deal with. Sometimes this is the desired effect.
We call the process of passing the copy a "By Value" call. We call the other process where we allow the calling DLL to change the actual variable a "By Reference" call. In other words, in the first instance we have only passed the VALUE of the variable, not the real variable. In the second instance we have passed the real variable by passing the memory location of that variable. The memory location is what references the variable.
These terms are used in Liberty Basic itself. We can pass variables into Functions and Subs either by reference (they can be changed) or by value (the default - they can not be changed). By default Liberty Basic passes all values to DLLs by value also. If we want to pass a value by reference to a DLL we must inform Liberty Basic of that intention.
If the value is a string we can do this by simply adding an ASCII zero to the end of the string. Liberty Basic does not pass that ASCII zero on to the DLL. It is just an indicator to pass by reference. All strings are automatically passed with a string terminator regardless of whether they are being passed by value (the default) or by reference. That terminator is already present in memory as we discussed earlier - it is an ASCII zero. This is where my understanding became clouded. If there already was a terminator, then why was I explicitly adding an ASCII zero to my strings when I passed them?
Well the answer was that sometimes I needed to, and other times I did not and I just did not understand what I was doing. I have seen other people get bogged down here too. The thing to remember, the thing Alyce finally drilled home, is HOW are we passing the string? - If we want the DLL requires the string to be passed by reference then we MUST add the extra ASCII zero. If the DLL requires the string to be passed by value then do not add the extra ASCII zero - LB will insure the string is properly terminated.
But what do you do if you need to pass a numeric value by reference? These are also passed by value by default. You can't just add an ASCII zero to one of these.
There is a work around for this as well. Create a struct with a single numeric of the proper type. Before the call set the variable in the struct to the correct value and then pass the whole thing as a pointer. (Structs must be passed as pointers which are by definition by reference only.) Here is an example:
Struct myVal, count as long
myVal.count.struct = 40
calldll #mydll, "count_ducks", myVal as ptr, result as long
In closing, Alyce was kind enough to point me to the proper reference in the Liberty Basic Helpfile, which to my embarrassment explains all of this. I had missed this part of the helpfile (or more precisely, glossed over it) more than once. It is another case where we learn everyday, and material which seems old is never really old - there is always something new and fresh there:
From the helpfile: Passing Strings into API/DLL calls:
Liberty BASIC provides two ways to pass strings as parameters into API/DLL calls. By default when passing a string as a parameter, Liberty BASIC copies the string and adds an ASCII zero to the end of the copy. This ASCII zero is called a "null terminator." Most Windows API calls expect this kind of zero-terminated string. This technique provides a copy of the original string to be passed to the API function. If the function then modifies that string directly (as in the kernel API function GetProfileString and a few others), then the Liberty BASIC application calling the function cannot access the modified string because only the copy is modified.
The way to fix this is for the code to include the ASCII zero onto the end of the string that will be used as a parameter in the API call. Liberty BASIC checks for the ASCII zero, and if it finds it, passes the memory address of original string and does not make a copy. This is only necessary if an application passes a string, expecting to get it back modified by the API or DLL function called.
Here is a code segment to get printer info using the GetProfileString API call:
'getpstr.bas - Get information using the GetProfileString API call
open "kernel32.dll" for dll as #kernel
'Notice that no ASCII zero characters are added to these strings
'because the program will not need to read any results out of the call
'to GetProfileString.
appName$ = "windows"
keyName$ = "device"
default$ = ""
'add an ASCII zero so Liberty BASIC will not pass a copy of result$
'into the API call, but the actual contents of result$
result$ = space$(49)+chr$(0)
size = 50 '49 spaces plus 1 ASCII zero character
calldll #kernel, "GetProfileStringA",_
appName$ as ptr,_
keyName$ as ptr,_
default$ as ptr,_
result$ as ptr,_
size as long,_
result as long
close #kernel
'display the retrieved information up to but not including the
'terminating ASCII zero
print left$(result$, instr(result$, chr$(0)) - 1)
WAIT
Eddie's Lessons
Beginners Programming